﻿/* ================================================================================
<copyright file="StatCounter.cs" company="The Framework Factory, LLC" lastWriteTime="12/05/2012 23:23:47 PM">
  Copyright (c) 2012 The Framework Factory, LLC.  All rights reserved.
</copyright>
=================================================================================== */
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;

using log4net.Core;
using log4net.Util;

namespace FWF.Logging.Statistics
{
    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    public delegate long FetchValueDelegate();

    /// <summary>
    /// 
    /// </summary>
    public class StatCounter
    {
        private IFWFLog _log;
        private string _category;
        private string _name;
        private string _instance;
        private long _value;

        private DateTime _lastSnapShot = DateTime.MinValue;
        private TimeSpan _snapShotExpiration = TimeSpan.FromSeconds(10);

        private static readonly Thread _thread;
        private static readonly ManualResetEvent _manualResetEvent;

        private static volatile List<StatCounter> _counters = new List<StatCounter>();
        private static volatile object _lockObject = new object();

        #region Constructors / Finalizer

        /// <summary>
        /// Initializes the <see cref="StatCounter"/> class.
        /// </summary>
        static StatCounter()
        {
            _manualResetEvent = new ManualResetEvent(false);

            _thread = new Thread(BackgroundWork);
            _thread.Name = "StatCounter Working Thread";
            _thread.IsBackground = true;
            _thread.Start();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TimeTaken"/> class.
        /// </summary>
        protected StatCounter()
        {
        }

        #endregion

        /// <summary>
        /// Creates the specified log.
        /// </summary>
        /// <param name="log">The log.</param>
        /// <param name="category">The category.</param>
        /// <param name="name">The name.</param>
        /// <returns></returns>
        public static StatCounter Create(
            IFWFLog log,
            string category,
            string name
            )
        {
            return Create( 
                log, 
                category, 
                name, 
                TimeSpan.Zero 
                );
        }

        /// <summary>
        /// Creates the specified log.
        /// </summary>
        /// <param name="log">The log.</param>
        /// <param name="category">The category.</param>
        /// <param name="name">The name.</param>
        /// <param name="snapShotDuration">Duration of the snap shot.</param>
        /// <returns></returns>
        public static StatCounter Create(
            IFWFLog log,
            string category,
            string name,
            TimeSpan snapShotDuration
            )
        {
            return Create(
                log, 
                category, 
                name, 
                snapShotDuration, 
                string.Empty
                );
        }

        /// <summary>
        /// Creates the specified log.
        /// </summary>
        /// <param name="log">The log.</param>
        /// <param name="category">The category.</param>
        /// <param name="name">The name.</param>
        /// <param name="snapShotDuration">Duration of the snap shot.</param>
        /// <param name="instance">The instance.</param>
        /// <returns></returns>
        public static StatCounter Create(
            IFWFLog log,
            string category,
            string name,
            TimeSpan snapShotDuration,
            string instance
            )
        {

            lock (_lockObject)
            {
                StatCounter statCounter = _counters.Find(
                    x =>
                    x.Category.Equals(category) &&
                    x.Name.Equals(name) &&
                    x.Instance.Equals(instance)
                    );

                if (statCounter != null)
                {
                    return statCounter;
                }

                statCounter = new StatCounter();
                statCounter._log = log;
                statCounter._category = category;
                statCounter._name = name;
                statCounter._instance = instance;
                statCounter._snapShotExpiration = snapShotDuration;

                _counters.Add(statCounter);

                return statCounter;
            }
        }

        /// <summary>
        /// Gets the category.
        /// </summary>
        /// <value>The category.</value>
        public string Category
        {
            get
            {
                return _category;
            }
        }

        /// <summary>
        /// Gets the name.
        /// </summary>
        /// <value>The name.</value>
        public string Name
        {
            get
            {
                return _name;
            }
        }

        /// <summary>
        /// Gets or sets the instance.
        /// </summary>
        /// <value>The instance.</value>
        public string Instance
        {
            get
            {
                return _instance;
            }
        }

        /// <summary>
        /// Gets or sets the fetch value method.
        /// </summary>
        /// <value>The fetch value method.</value>
        public FetchValueDelegate FetchValueMethod
        {
            get;
            set;
        }

        /// <summary>
        /// Increments this instance.
        /// </summary>
        /// <returns></returns>
        public long Increment()
        {
            return Interlocked.Increment(ref _value);
        }

        /// <summary>
        /// Increments the by.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public long IncrementBy(long value)
        {
            lock (_lockObject)
            {
                _value += value;
            }

            return _value;
        }

        /// <summary>
        /// Decrements this instance.
        /// </summary>
        /// <returns></returns>
        public long Decrement()
        {
            return Interlocked.Decrement(ref _value);
        }

        /// <summary>
        /// Backgrounds the work.
        /// </summary>
        /// <param name="args">The args.</param>
        private static void BackgroundWork(object args)
        {
            // NOTE: Iterate through the static counter collection and perform 
            // snapshots for every counter based upon the snapShotExpiration value

            while (true)
            {
                IEnumerable<StatCounter> listCounters;

                lock ( _lockObject )
                {
                    listCounters = _counters.FindAll(
                        x =>
                        x._snapShotExpiration != TimeSpan.Zero &&
                        x._lastSnapShot.Add( x._snapShotExpiration ) < DateTime.Now
                        );
                }

                //
                foreach ( var statCounter in listCounters )
                {
                    statCounter.PerformSnapShot();
                    statCounter._lastSnapShot = DateTime.Now;
                }

                bool signaled = _manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(100));

                if (signaled)
                {
                    break;
                }
            }
        }

        /// <summary>
        /// Stops this instance and records information to the <see cref="IFWFLog"/>.
        /// </summary>
        public void PerformSnapShot()
        {
            if (FetchValueMethod != null)
            {
                _value = FetchValueMethod.Invoke();
            }

            // NOTE: Record a normal log entry for the stat counter
            if (_log.IsTraceEnabled)
            {
                var logData = new LoggingEventData();
                logData.Level = Level.Trace;
                logData.Message = string.Format(
                    "{0}:{1}:{2} {3}",
                    _category,
                    _name,
                    _instance,
                    _value
                    );

                _log.Log(logData);
            }

            // NOTE: Create a special log entry with properties of the duration of the task
            if (_log.IsStatisticsEnabled)
            {
                var logData = _log.PrepareLogEvent( typeof( StatCounter ) );
                logData.Level = ExtendedLogImplementation.Statistics;

                logData.Properties = new PropertiesDictionary();
                logData.Properties["stat.name"] = _name;
                logData.Properties["stat.category"] = _category;
                logData.Properties["stat.instance"] = _instance;
                logData.Properties["stat.value"] = _value.ToString( CultureInfo.CurrentCulture );

                _log.Log(logData);
            }
        }

    }
}
